#include "UnitTactics.h"
#include <algorithm>
#include "ThreatMap.h"

UnitTactics::UnitTactics()
{
  // Default attack
  this->addPriority(new UnitAction(
    UnitTypes::Men,
    240,
    &this->default,
    10
  ));
  // No tactic fired, attack/move towards the goal
  this->addPriority(new UnitAction(
    UnitTypes::None,
    0,
    &this->fallback,
    0
  ));
}

UnitTactics::~UnitTactics()
{
  // remove_if(this->priority.begin(), this->priority.end(), &this->deletePriority);
  while (!this->priority.empty()) {
    delete *this->priority.begin();
    this->priority.erase(this->priority.begin());
  }
}

UnitTactics::typeState* UnitTactics::newState()
{
  return new typeState;
}

void UnitTactics::delState(typeState* state)
{
  delete state;
}

void UnitTactics::update(UnitState* state)
{
  // iterate through priorities
  for each (Action* p in this->priority) {
    // does this priority trigger?
    // splitting it into two sections to handle either bullets or units; this is
    // unlikely the most elegant way to deal with the situation, but it works
    if (p->actionType == UnitTactics::Action::BULLET){
      UnitTactics::BulletAction* bP = (UnitTactics::BulletAction*)p;
      std::set<Bullet*> bullets = this->scanUnitRadius(state, bP->target, bP->rangeDist);
      // if the test returns true, stop
      if ((!bullets.empty() || bP->target == BulletTypes::None) && bP->conditionTest(state, bullets))
        break;
    } else if (p->actionType == UnitTactics::Action::UNIT){
      UnitTactics::UnitAction* uP = (UnitTactics::UnitAction*)p;
      std::set<Unit*> units = this->scanUnitRadius(state, uP->target, uP->rangeDist);
      // if the test returns true, stop
      if ((!units.empty() || uP->target == UnitTypes::None) && uP->conditionTest(state, units))
        break;
    }
  }
}

void UnitTactics::addPriority(Action *p)
{
  this->priority.insert(p);
}

bool UnitTactics::deletePriority(Action* p){
  delete p;
  return true;
}

std::set<Unit*> UnitTactics::scanUnitRadius(UnitState* engager, UnitType target, int radius)
{
  std::set<Unit*> group;
  // Get all units in radius
  for each (Unit* u in engager->unit->getUnitsInRadius(radius)) {
    // Check the type and alliance; we also do a more in depth check for 
    // types that encompass more than one type
    if (u->getPlayer()->isEnemy(Broodwar->self())) {
      if (u->getType() == target || (!u->getType().isBuilding() && target == UnitTypes::Men)) {
        group.insert(u);
      }
    }
  }

  return group;
}

std::set<Bullet*> UnitTactics::scanUnitRadius(UnitState* engager, BulletType target, int radius)
{
  std::set<Bullet*> group;
  // TODO

  return group;
}

int UnitTactics::fallback(UnitState* engager, std::set<Unit*> triggers)
{
  if (engager->method == EngageTypes::Attack) {
    engager->unit->attack(engager->target);
  } else {
    engager->unit->move(engager->target);
  }
  return 1;
}

int UnitTactics::default(UnitState* engager, std::set<Unit*> triggers)
{
  if (engager->method == EngageTypes::Attack) {
    return attack(engager, triggers);
  } else if (engager->method == EngageTypes::Avoid) {
    return avoid(engager, triggers);
  }

  return 0;
}

int UnitTactics::attack(UnitState* engager, std::set<Unit*> triggers)
{
  // Quit early if this unit cannot attack
  UnitType engagerType = engager->unit->getType();
  if (!engagerType.canAttack())
    return 0;

  // Quit if this unit is in an attack frame
  if (engager->unit->isAttackFrame())
    return 0;

  Unit* best = NULL;
  // Find best target
  for each (Unit* enemy in triggers) {
    if (!best || 
        enemy->getType().isBuilding() < best->getType().isBuilding() ||
        enemy->getHitPoints() < best->getHitPoints()) {
      best = enemy;
    }
  }

  if (!best)
    return 0;

  std::list<Potential*> potentials;

  // Check if we should attack our target
  WeaponType weapon = engagerType.groundWeapon();
  int weaponCooldown = engager->unit->getGroundWeaponCooldown();
  if (best->getType().isFlyer()) {
    weapon = engagerType.airWeapon();
    weaponCooldown = engager->unit->getAirWeaponCooldown();
  }

  if (weaponCooldown < weapon.damageCooldown()) {
    int dist = engager->unit->getDistance(best);
    if (dist < weapon.maxRange() && 
        dist > weapon.minRange()){
      engager->unit->attack(best);
      return 1;
    }

    // Create potential towards target if cooldown is more than half over
    Position pos = best->getPosition();
    Potential target(-Potential::epsilon*1.1, weapon.maxRange(), &pos);
    Broodwar->drawCircle(CoordinateType::Map,pos.x(),pos.y(),weapon.maxRange(),Colors::Green,false);
    potentials.push_back(&target);
  }

  // Potential towards the goal
  //Potential goal(-Potential::epsilon*1.1, engagerType.seekRange(), &engager->target);
  //potentials.push_back(&goal);

  Position* moveTo = TheThreatMap->calculateMove(engager->unit, potentials);
  engager->unit->attack(*moveTo);

  return 1;
}

int UnitTactics::avoid(UnitState* engager, std::set<Unit*> triggers)
{
  return 1;
}
